/*	____________________________________________________________________________

	          Parser Component Implementation for the PhIMP Compiler
	____________________________________________________________________________  */


#include <iostream>
#include <fstream>
using namespace std;

extern ifstream sourceFile;
extern ofstream outFile, listFile;

#include "scan.h"   // Scanner class definition
#include "parse.h"  // Parser class definition
#include "code.h"   // CodeGen class definition

extern Scanner scan; // global scanner declared in PhiMP.cpp
extern CodeGen code; // global code generator declared in PhiMP.cpp

Parser::Parser()
{
	tokenAvailable = false;  // no token initially available to parser
}

void Parser::Match(Token t)
{
	if (t != NextToken())
		SyntaxError();
	else
		tokenAvailable = false;
}

Token Parser::NextToken()
{
	if (!tokenAvailable)
	{
		savedToken = scan.GetNextToken();
		tokenAvailable = true;
	}
		return savedToken;
}

void Parser::Warning(string additionalMsg)
{
	cout << " * Warning Detected on line #" << scan.lineNumber << endl;
	listFile << " * Warning Detected on line #" << scan.lineNumber << endl;

	if(additionalMsg != "")
	{
		cout << " * " << additionalMsg << endl;
		listFile << " * " << additionalMsg << endl;
	}
}

void Parser::SyntaxError(string additionalMsg)
{
	cout << " *** Syntax Error Detected near '" << scan.tokenBuffer << "' on line #" << scan.lineNumber << endl;
	listFile << " *** Syntax Error Detected near '" << scan.tokenBuffer << "' on line #" << scan.lineNumber << endl;

	if(additionalMsg != "")
	{
		cout << " *** " << additionalMsg << endl;
		listFile << " *** " << additionalMsg << endl;
	}

	sourceFile.close();
	listFile.close();
	outFile.close();
	cin.ignore(256, '\n');
	cin.get();
	exit(1);  // error termination
}

void Parser::SystemGoal()
{
	Program();
	Match(EOF_SYM);
	code.Finish();  /*** CODE ***/
}
void Parser::Program()         
{
	code.Start();  /*** CODE ***/
	Match( PROGRAM_SYM );
	Match( ID );

	if (NextToken() == VAR_SYM)
		VarList();

	while (NextToken() == FUNCT_SYM)
		Function();

	Match( LCURL );
	StatementList(0);
	Match( RCURL );
}

void Parser::IdList()
{
	ExprRec identifier;

	//TEMP COUT STATEMENT
	//cout << identifier.name << scan.tokenBuffer << endl;

	Ident(identifier);

	//TEMP COUT STATEMENT
	//cout << identifier.name << scan.tokenBuffer << endl;

	code.ReadId(identifier);  /*** CODE ***/
	while (NextToken() == COMMA)
	{
			Match(COMMA);
			Ident(identifier);
			code.ReadId(identifier);  /*** CODE ***/
	}
}

void Parser::Ident(ExprRec& result)
{
	//TEMP COUT STATEMENT
	//cout << tokenAvailable << scan.tokenBuffer << endl;

	Match(ID);

	//TEMP COUT STATEMENT
	//cout << tokenAvailable << scan.tokenBuffer << endl;

	result.name = scan.tokenBuffer;
	result.kind = ID_EXPR;
	code.CheckId(result);  /*** CODE ***/
	//code.ProcessId(result);  /*** CODE ***/
}

void Parser::VarList()
{
	Match( VAR_SYM );
	DeclarationList();
}

void Parser::DeclarationList()
{
	DeclareParam();
	while (NextToken() != FUNCT_SYM && NextToken() != LCURL)
	{ // We keep going unless they started function definitions
		DeclareParam();
	}
}

void Parser::DeclareParam()
{
	ExprRec identifier, initVar;
	bool negate = false;

	DataType(identifier); // Get the identifier datatype
	Match(ID);
	code.ProcessId(identifier);  /*** CODE ***/
	//Ident(identifier);    // create the identifier



	switch( NextToken() )
	{
	case COLON:					// HANDLE ARRAY INITIALIZATION HERE (A level) XXX FIX ME
		Match(COLON);
		Literal(identifier);

		if( identifier.dataType != INT )
			SyntaxError("Array must be of integer size");

		InitArray(initVar);		// initialize it
		Match(SEMICOLON);
		break;
	case SEMICOLON:
		Match(SEMICOLON);
		break;
	default:
		negate = InitVar(initVar);		// initialize it
		code.Assign(identifier, initVar);
		if( negate )
			code.Negate(identifier);
		Match(SEMICOLON);
		break;
	}
}


void Parser::DataType(ExprRec& result)
{
	switch(NextToken() )
	{
	case INT_TYPE:
		result.dataType = INT;
		Match(INT_TYPE);
		break;
	case REAL_TYPE:
		result.dataType = REAL;
		Match(REAL_TYPE);
		break;
	case CHAR_TYPE:
		result.dataType = CHAR;
		Match(CHAR_TYPE);
		break;
	case BOOL_TYPE:
		result.dataType = BOOL;
		Match(BOOL_TYPE);
		break;
	default:
		SyntaxError("Not a valid datatype!");
		break;
	}
}

bool Parser::InitVar(ExprRec& result)
{
	bool negate = false;
	switch( NextToken() )
	{
	case ASSIGN_OP:
		Match(ASSIGN_OP);
		negate = UnaryOp();
		Literal(result);
		break;
	default: break;
	}
	return negate;
}

void Parser::InitArray(ExprRec& result)
{
	switch(NextToken())
	{
	case ASSIGN_OP:
		Match(ASSIGN_OP);
		Match(LCURL);
		Literal(result);

		while (NextToken() == COMMA)
		{
			Match(COMMA);
			Literal(result);
		}
		Match(RCURL);
		break;
	default: break;
	}
}

void Parser::Function()
{
	ExprRec expr;

	Match( FUNCT_SYM );
	Match( ID );
	Match(LPAREN);
	if ( NextToken() != RPAREN )
		ParamList();
	Match(RPAREN);

	if ( NextToken() == VAR_SYM )
		VarList();

	Match( LCURL );
	StatementList(0);
	Match( RCURL );
	if ( NextToken() == RETURN_SYM )
		Return(expr);

}

void Parser::ParamList()
{
	DeclareParam();
	while ( NextToken() == COMMA )
	{ // We keep going unless they started function definitions
		Match( COMMA );
		DeclareParam();
	}
}

void Parser::Return(ExprRec& result)
{
	Match( RETURN_SYM );
	Expression(result);
}

void Parser::ExprList( bool isWrite )
{
	bool negate = false;
	ExprRec expr;
	//cout << "token in the expression function: " << scan.tokenBuffer << endl;

	//negate = UnaryOp();

	Expression(expr);

	//if( negate )
	//	code.Negate(expr);

	if( isWrite )
	{
		code.WriteExpr(expr);
	}

	//cout << "token in the expression function: " << scan.tokenBuffer << endl;
	while(NextToken() == COMMA)
	{
		//cout << "token in the expression function: " << scan.tokenBuffer << endl;
		//cout << expr.name << endl;
		Match(COMMA);
		//cout << "token in the expression function: " << scan.tokenBuffer << endl;

		//negate = UnaryOp();
		Expression(expr);
		//if( negate )
		//	code.Negate(expr);

		if( isWrite )
		{
			code.WriteExpr(expr);
		}
	}
}

bool Parser::UnaryOp()
{
	switch(NextToken())
	{
	case MINUS_OP:
		Match(MINUS_OP);
		return true;
	default: break;
	}
	return false;
}

void Parser::Expression(ExprRec& result)
{
	ExprRec leftOperand, rightOperand;
	OpRec op;
	bool negate = false;
	negate = UnaryOp();
	Factor(result);
	if(negate)
		code.Negate(result);
	while(AddOp(op)) 
	{
		leftOperand.kind = result.kind; 
		leftOperand.val = result.val;
		leftOperand.name= result.name;
		leftOperand.dataType = result.dataType;
		negate = UnaryOp();
		Factor(rightOperand);
		if( negate )
			code.Negate(result);

		/*** CODE ***/
		code.GenInfix (leftOperand, op, rightOperand, result);
	}
}

void Parser::Factor(ExprRec& result)
{
	ExprRec leftOperand, rightOperand;
	OpRec op;

	Term(result); // It must have at least ONE primary
	while(MultOp(op))
	{
		leftOperand.kind = result.kind;
		leftOperand.val = result.val;
		leftOperand.name = result.name;
		leftOperand.dataType = result.dataType;
		Term(rightOperand);

		/*** CODE ***/
		code.GenInfix(leftOperand, op, rightOperand, result);
	}
}

void Parser::Term(ExprRec& result)
{
	ExprRec leftOperand, rightOperand;
	OpRec op;

	Primary(result);
	switch(NextToken())
	{
	case POW_OP:
		Match(POW_OP);
		code.ProcessOp(op);  /*** CODE ***/

		leftOperand.kind = result.kind;
		leftOperand.val = result.val;
		leftOperand.name = result.name;
		leftOperand.dataType = result.dataType;
		Primary(result);

		/*** CODE ***/
		code.GenInfix(leftOperand, op, rightOperand, result);
		break;
	}
}

void Parser::Primary(ExprRec& result)
{
	ExprRec leftOperand;
	OpRec op;

	switch (NextToken())
	{
	/*
	case MINUS_OP:
		Match(MINUS_OP);
		switch (NextToken())
		{
		case LPAREN:
			Match(LPAREN);
			Expression(result);
			Match(RPAREN);
			break;
		case ID:
			VarOrArray(result);
			break;
		}
		if(result.dataType == INT || result.dataType == REAL)
			leftOperand.kind = LITERAL_EXPR; 
			leftOperand.val = 0;
			leftOperand.name = "0";
			leftOperand.dataType = result.dataType;
			op.op = MINUS;
			// CODE
			code.GenInfix (leftOperand, op, result, result);
		break;*/
	case LPAREN:
		Match(LPAREN);
		Expression(result);
		Match(RPAREN);
		break;
	case ID:
		VarOrArray(result);
		break;
	default:
		Literal(result);
	}
}

void Parser::Literal(ExprRec& result)
{
	OpRec op;

	switch (NextToken())
	{
	/*case MINUS_OP:
		Match(MINUS_OP);
		code.ProcessOp(op);  //CODE
		switch (NextToken())
		{
		case INT_LITERAL:
			Match(INT_LITERAL);
			result.dataType = INT;
			code.ProcessLiteral(result);  //CODE
			result.val = -result.val;
			break;
		case REAL_LITERAL:
			Match(REAL_LITERAL);
			result.dataType = REAL;
			code.ProcessLiteral(result);  //CODE
			result.val = -result.val;
			break;
		}
		break;*/
	case INT_LITERAL:
		Match(INT_LITERAL);
		result.dataType = INT;
		code.ProcessLiteral(result);  /*** CODE ***/;
		break;
	case REAL_LITERAL:
		Match(REAL_LITERAL);
		result.dataType = REAL;
		code.ProcessLiteral(result);  /*** CODE ***/
		break;
	case BOOL_LITERAL:
		Match(BOOL_LITERAL);
		result.dataType = BOOL;
		code.ProcessLiteral(result);  /*** CODE ***/
		break;
	case CHAR_LITERAL:
		Match(CHAR_LITERAL);
		result.dataType = CHAR;
		code.ProcessLiteral(result);  /*** CODE ***/
		break;
	case STR_LITERAL:
		Match(STR_LITERAL);
		result.dataType = STRING;
		code.ProcessLiteral(result);  /*** CODE ***/
		break;
	default:
		//cout << scan.tokenBuffer << endl;
		SyntaxError();
	}
}

void Parser::BoolExpr(ExprRec& result, bool isIf, ExprRec& nextAddr)
{
	ExprRec leftOperand, rightOperand;
	OpRec op;

	Relation(result, isIf, nextAddr); // It must have at least ONE primary
	/*while(LogOp(op))
	{
		leftOperand.kind = result.kind;
		leftOperand.val = result.val;
		leftOperand.name = result.name;
		leftOperand.dataType = result.dataType;
		Relation(rightOperand);

		code.GenInfix(leftOperand, op, rightOperand, result);
	} */
}

void Parser::Relation(ExprRec& result, bool isLoop, ExprRec& nextAddr)
{
	/* switch (NextToken())
	{
	case ID:
		BoolVarOrArray(result);
		break;
	default: */
		ExprRec leftOperand, rightOperand;
		OpRec op;

		Expression(result); // It must have at least ONE primary

		if( RelOp(op) )
		{
			leftOperand.kind = result.kind;
			leftOperand.val = result.val;
			leftOperand.name = result.name;
			leftOperand.dataType = result.dataType;
			Expression(rightOperand);
			/*** CODE ***/
			code.BoolGenInfix(leftOperand, op, rightOperand, result, isLoop, nextAddr);
		}
		else
			SyntaxError("Must have a relational op.");
		return;
	//}
}

void Parser::BoolVarOrArray(ExprRec& result)
{
	VarOrArray(result);
}

void Parser::VarOrArray(ExprRec& result)
{
	ExprRec lit;
	Ident(result);
	//code.ExtractExpr(result);

	switch (NextToken())
	{
	case LBRACKET:
		Match(LBRACKET);
		Literal(lit);
		if( lit.dataType != INT )
			SyntaxError("You must reference an array with an integer value!");

		code.ProcessLiteral(lit);  /*** CODE ***/
		Match(RBRACKET);
	}

}


bool Parser::AddOp(OpRec& op)
{
	switch (NextToken())
	{
	case PLUS_OP:
		Match(PLUS_OP);
		code.ProcessOp(op);  /*** CODE ***/
		return true;
	case MINUS_OP:
		Match(MINUS_OP);
		code.ProcessOp(op);  /*** CODE ***/
		return true;
	default:
		return false;
	}
}

bool Parser::MultOp(OpRec& op)
{
	switch(NextToken())
	{
	case MULT_OP:	// Check for a * or a / and write the code
		Match(MULT_OP);
		code.ProcessOp(op);
		return true;
	case DIV_OP:
		Match(DIV_OP);
		code.ProcessOp(op);
		return true;
	case MOD_OP:
		Match(MOD_OP);
		code.ProcessOp(op);
		return true;
	case INT_DIV_OP:
		Match(INT_DIV_OP);
		code.ProcessOp(op);
		return true;
	default:
		return false;
	}
}

bool Parser::LogOp(OpRec& op)
{
	switch(NextToken())
	{
	case AND_OP:
		Match(AND_OP);
		code.ProcessOp(op);
		return true;
	case OR_OP:
		Match(OR_OP);
		code.ProcessOp(op);
		return true;
	default:
		return false;
	}
}

bool Parser::RelOp(OpRec& op)
{
	switch(NextToken())
	{
	case LT_OP:
		Match(LT_OP);
		code.ProcessOp(op);
		return true;
	case LTE_OP:
		Match(LTE_OP);
		code.ProcessOp(op);
		return true;
	case EQ_OP:
		Match(EQ_OP);
		code.ProcessOp(op);
		return true;
	case GTE_OP:
		Match(GTE_OP);
		code.ProcessOp(op);
		return true;
	case GT_OP:
		Match(GT_OP);
		code.ProcessOp(op);
		return true;
	case NE_OP:
		Match(NE_OP);
		code.ProcessOp(op);
		return true;
	default:
		return false;
	}
}

void Parser::Statement(int forCount)
{
	ExprRec identifier, expr, leftOperand, rightOperand, nextAddress;
	bool isIf;
	OpRec op;

	switch (NextToken())
	{
	case ID:
		VarOrArray(identifier);
		Match(ASSIGN_OP);
		Expression(expr);

		if ( RelOp(op) )
		{
			leftOperand.kind = expr.kind;
			leftOperand.val = expr.val;
			leftOperand.name = expr.name;
			leftOperand.dataType = expr.dataType;
			Expression(rightOperand);

			/*** CODE ***/
			code.BoolGenInfix(leftOperand, op, rightOperand, expr, isIf, nextAddress);
		}

		/*while(LogOp(op))
		{
			leftOperand.kind = expr.kind;
			leftOperand.val = expr.val;
			leftOperand.name = expr.name;
			leftOperand.dataType = expr.dataType;
			Relation(rightOperand);

			code.GenInfix(leftOperand, op, rightOperand, expr);
		}*/

		code.Assign(identifier, expr);  /*** CODE ***/
		Match(SEMICOLON);
		break;
	case IF_SYM:
		IfStatement();
		break;
	case FOR_SYM:
		ForLoop(forCount);
		break;
	case WHILE_SYM:
		WhileLoop();
		break;
	case READ_SYM:
		ReadStatement();
		break;
	case WRITE_SYM:
		WriteStatement();
		break;
	case WRITELN_SYM:
		WritelnStatement();
		break;
	default:
		SyntaxError();
	}
}

void Parser::StatementList(int forCount)
{
	Statement(forCount);
	for (;;) 
	{  // loop exited by return
		switch (NextToken())
		{
		case IF_SYM:
		case WHILE_SYM:
		case FOR_SYM:
		case ID:
		case READ_SYM:
		case WRITE_SYM:
		case WRITELN_SYM:
			Statement(forCount);
			break;
		default:
			return;
		}
	}
}

void Parser::IfStatement()
{
	ExprRec result;
	ExprRec nextElifAddr, nextElifAddr2;
	nextElifAddr.dataType = ADDRESS;
	code.Enter(nextElifAddr);
	code.Enter(nextElifAddr2);


	bool isIf = true;

	Match(IF_SYM);
	Match(LPAREN);

	BoolExpr(result, isIf, nextElifAddr);

	code.StartIfStatement(result, nextElifAddr);

	Match(RPAREN);
	Match(LCURL);

	StatementList(0);

	Match(RCURL);

	// ElifStatement(nextElifAddr, isIf, nextElifAddr);

	ElseStatement(nextElifAddr);

	//code.EndIfStatement(nextElifAddr);
}

void Parser::ElifStatement(ExprRec& elifAddr, bool isIf, ExprRec& nextAddr)
{
	while( NextToken() == ELIF_SYM )
	{
		ExprRec result;

		Match(ELIF_SYM);
		Match(LPAREN);

		BoolExpr(result, isIf, nextAddr);
		
		code.ElifStatement(result, elifAddr);

		Match(RPAREN);
		Match(LCURL);

		StatementList(0);

		Match(RCURL);
	}
}

void Parser::ElseStatement(ExprRec& elseAddr)
{
	ExprRec nextElifAddr;
	nextElifAddr.dataType = ADDRESS;

	code.Enter(nextElifAddr);
	//code.EndIfStatement(nextElifAddr);

	if( NextToken() == ELSE_SYM )
	{
		ExprRec result;

		Match(ELSE_SYM);

		Match(LCURL);
		
		code.ElseStatement(elseAddr, nextElifAddr);

		StatementList(0);

		Match(RCURL);

		code.EndElseStatement(nextElifAddr);
	}
}

void Parser::ForLoop(int forCount)
{
	ExprRec iterator;
	ExprRec start;
	ExprRec end;
	ExprRec step;
	ExprRec startLabel;
	ExprRec endLabel;

	Match(FOR_SYM);
	VarOrArray(iterator);
	Match(ASSIGN_OP);
	ForPrimary(start);
	Match(COMMA);
	ForPrimary(end);
	Match(COMMA);
	ForPrimary(step);
	Match(LCURL);

	code.StartForLoop(start, end, startLabel, endLabel, forCount);
	
	StatementList(forCount+2);

	Match(RCURL);

	code.EndForLoop(start, step, startLabel, endLabel, forCount);
}

void Parser::ForPrimary(ExprRec &result)
{
	switch (NextToken())
	{
	case INT_LITERAL:
		Match(INT_LITERAL);
		result.dataType = INT;
		code.ProcessLiteral(result);  /*** CODE ***/;
		break;
	default: VarOrArray(result);
	}
}

void Parser::WhileLoop()
{
}

void Parser::ReadStatement()
{
	Match(READ_SYM);
	Match(LPAREN);
	IdList();
	Match(RPAREN);
	Match(SEMICOLON);
}

void Parser::WriteStatement()
{
	Match(WRITE_SYM);
	Match(LPAREN);
	ExprList(true);
	Match(RPAREN);
	Match(SEMICOLON);
}

void Parser::WritelnStatement()
{
	Match(WRITELN_SYM);
	Match(LPAREN);
	ExprList(true);
	Match(RPAREN);
	Match(SEMICOLON);
	code.NewLine();  /*** CODE ***/
}